package cn.dev8

import cn.dev8.util.*
import lombok.SneakyThrows
import nbcp.base.comm.const
import nbcp.base.extend.HasValue
import nbcp.base.utils.FileUtil
import nbcp.base.utils.ShellUtil
import nbcp.base.utils.XmlUtil
import org.apache.maven.plugin.AbstractMojo
import org.apache.maven.plugin.MojoExecutionException
import org.apache.maven.plugins.annotations.LifecyclePhase
import org.apache.maven.plugins.annotations.Mojo
import org.apache.maven.plugins.annotations.Parameter
import org.apache.maven.project.MavenProject
import org.dom4j.io.SAXReader
import java.io.File
import java.io.FileOutputStream
import java.io.FileWriter
import java.io.OutputStreamWriter
import java.nio.charset.StandardCharsets
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.*
import java.util.jar.JarFile

/**
 * Goal which touches a timestamp file.
 *
 */
@Mojo(name = "split-lib", defaultPhase = LifecyclePhase.PACKAGE)
class SplitLibMojo

    : AbstractMojo() {
    /**
     * Location of the file.
     */
    @Parameter(defaultValue = "\${project.build.directory}", property = "outputDir", required = true)
    var outputDirectory: File? = null


    @Parameter(defaultValue = "\${project}", readonly = true, required = true)
    var project: MavenProject? = null

    /**
     * 默认仅保留和该项目相同的 groupId
     */
    @Parameter(property = "keepGroupIds", defaultValue = "")
    var keepGroupIds: String? = null

    @Parameter(property = "skip", defaultValue = "false")
    var skip: Boolean? = null


    @Parameter(property = "override", defaultValue = "false")
    var override: Boolean? = null

    var jdkJarCmdPath: String = ""

    var jarFile: File? = null

    var nowString: String = ""

    var classifier: String = ""

    @SneakyThrows
    @Throws(MojoExecutionException::class)
    override fun execute() {
        if (skip!!) {
            return
        }

        val now = LocalDateTime.now()
        nowString = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"))

        if (!outputDirectory!!.exists()) {
            throw RuntimeException("找不到 $outputDirectory !")
        }


        val splitLibPath = initWorkPath()

        jdkJarCmdPath = findJdkCmdPath()

        val groupIds = getKeepGroupIds()

        jarFile = findJarClassifierFile()

        log.info("split-lib 拆包路径:" + jarFile!!.path + ", 拆分的包名为：" + java.lang.String.join(",", groupIds))
        extractJar(jarFile!!.path)


        val libFile = File(FileUtil.resolvePath(splitLibPath.path, "jar", "BOOT-INF", "lib"))
        if (libFile.exists() == false) {
            val msg = jarFile!!.name + " 中不存在 BOOT-INF/lib !"
            log.error(msg)
            throw RuntimeException(msg)
        }
        val libJars = libFile.listFiles()

        var keepCount = 0
        var splitCount = 0
        log.info("包名".fillWithPad(46, ' ') + "匹配的groupId")
        log.info("".fillWithPad(66, '-'))
        for (jar in libJars!!) {
            val groupId = getJarGroupId(jar)

            if (indexOfItemStartWith(groupIds, groupId) < 0) {
                jar.renameTo(File(FileUtil.resolvePath(splitLibPath.path, "lib", jar.name)))
                //                var key = StringUtil.fillWithPad(jar.getPath().substring(libFile.getPath().length() + 1), 48, ' ');
                splitCount++
            } else {
                val key = jar.path.substring(libFile.path.length + 1).fillWithPad(48, ' ')

                log.info(key + groupId.fillWithPad(16, ' ') + " √")
                keepCount++
            }
        }
        log.info("".fillWithPad(42, '﹊'))

        log.info("split-lib 拆到 target/lib " + splitCount + " 个Jar包, " + jarFile!!.name + " 中保留了 " + keepCount + " 个包！")

        if (keepCount == 0) {
            log.error("没有保留任何包!");
        }

        //        if (FileUtil.deleteAll(new File(FileUtil.joinPath(splitLibPath.getPath(), "tmp")), true) == false) {
//            getLog().warn("清理 tmp  失败！");
//        }
        val splitJarName = project!!.artifactId + "-" + project!!.version + "-split-jar.jar"
        zipJar(FileUtil.resolvePath(splitLibPath.path, "jar"))
        val readmeFileName = FileUtil.resolvePath(
            outputDirectory!!.path,
            "split-jar-readme.txt"
        )
        val writer = OutputStreamWriter(FileOutputStream(readmeFileName), StandardCharsets.UTF_8)

        try {
            writer.write("[" + project!!.name + "]" + const.line_break + "split-lib-maven-plugin 执行时间: " + nowString + const.line_break)
            writer.write("keepGroupIds : " + java.lang.String.join(",", groupIds) + const.line_break)
            writer.write("拆到lib文件下共: " + splitCount + " 个包 , 保留了 " + keepCount + " 个包" + const.line_break)
            writer.write("override: " + override + const.line_break)
            writer.write("运行：" + const.line_break)
            writer.write("java-" + System.getProperty("java.version") + " -Dloader.path=lib -jar " + splitJarName)
        } finally {
            writer.flush()
            writer.close()
        }



        if (override!!) {
            log.info("split-lib 删除原文件: " + jarFile!!.name)

            jarFile!!.delete()
            val newJarFile = File(FileUtil.resolvePath(outputDirectory!!.path, splitJarName))
            File(FileUtil.resolvePath(splitLibPath.path, jarFile!!.name)).renameTo(newJarFile)

            val libPath = File(FileUtil.resolvePath(outputDirectory!!.path, "lib"))
            if (libPath.deleteRecursively() == false) {
                throw RuntimeException("删除 lib 文件夹出错！")
            }

            File(FileUtil.resolvePath(splitLibPath.path, "lib")).renameTo(libPath)
        }
    }

    private fun findJarClassifierFile(): File {
        var jarFileName = project!!.artifactId + "-" + project!!.version
        if (this.classifier.HasValue) {
            jarFileName += "-" + this.classifier
        }
        jarFileName += ".jar"

        return File(outputDirectory, jarFileName)
    }


    @SneakyThrows
    private fun initWorkPath(): File {
        val splitLibPath = File(FileUtil.resolvePath(outputDirectory!!.path, "split-lib"))
        if (splitLibPath.exists() && splitLibPath.deleteRecursively()) {
            if (splitLibPath.exists() && splitLibPath.list().size > 0) {
                throw RuntimeException("删除文件夹: " + splitLibPath.path + " 失败！")
            }
        }

        splitLibPath.mkdirs()


        val doc = SAXReader().read(project!!.file)
        val plugins = XmlUtil.getElements(doc, "project/build/plugins/plugin")
        for (it in plugins) {
            val groupId = XmlUtil.getSingleElement(it, "groupId")!!.textTrim
            if (groupId != "org.springframework.boot") {
                continue
            }
            val artifactId = XmlUtil.getSingleElement(it, "artifactId")!!.textTrim
            if (artifactId != "spring-boot-maven-plugin") {
                continue
            }

            val layoutDom = XmlUtil.getSingleElement(it, "configuration/layout")
            if (layoutDom == null || layoutDom.textTrim != "ZIP") {
                val message =
                    "split-lib 检查不通过！ org.springframework.boot:spring-boot-maven-plugin.configuration.layout must be ZIP!"

                log.error(message)

                val writer =
                    FileWriter(FileUtil.resolvePath(outputDirectory!!.path, "split-lib", "split-lib-check.txt"))
                writer.write("[" + project!!.name + "]" + const.line_break + "split-lib-maven-plugin 执行时间: " + nowString + const.line_break + const.line_break)
                writer.write(message)
                writer.flush()
                writer.close()

                throw RuntimeException(message)
            }

            val classifierDom = XmlUtil.getSingleElement(it, "configuration/classifier")
            if (classifierDom != null) {
                classifier = classifierDom.textTrim
            }
            break
        }

        return splitLibPath
    }

    private fun findJdkCmdPath(): String {
        val osName = System.getProperty("os.name").lowercase(Locale.getDefault())
        var javaHomePath = System.getProperty("java.home")

        //修复！
        if (javaHomePath.endsWith("jre")) {
            javaHomePath = FileUtil.resolvePath(javaHomePath, "../")
        }

        var jarExePath = FileUtil.resolvePath(javaHomePath, "bin", "jar")
        if (osName.contains("windows")) {
            jarExePath += ".exe"
        }

        if (File(jarExePath).exists() == false) {
            val msg = "找不到 jar 命令: $jarExePath!  JAVA_HOME:$javaHomePath"
            log.error(msg)
            throw RuntimeException(msg)
        }

        return jarExePath
    }

    private fun getKeepGroupIds(): Set<String> {
        val groupIds = HashSet<String>()

        var groupIdString = project!!.groupId
        val groupIdsArray =
            groupIdString.split("\\.").dropLastWhile { it.isEmpty() }.toMutableList()
        if (groupIdsArray.size > 1) {
            groupIdsArray.removeLast()
            groupIdString = java.lang.String.join(".", groupIdsArray)
        }


        if (keepGroupIds != null && keepGroupIds!!.length > 0) {
            for (it in keepGroupIds!!.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
                groupIds.add(it)
            }
        }

        if (groupIds.size == 0) {
            groupIds.add(groupIdString)
        }

        return groupIds
    }


    fun indexOfItemStartWith(list: Set<String>, item: String?): Int {
        if (item == null) return -1
        var index = -1
        for (it in list) {
            index++
            if (item.startsWith(it)) {
                return index
            }
        }
        return -1
    }

    private fun zipJar(workPath: String) {
        val cmd = ArrayList<String>()
        cmd.add(jdkJarCmdPath)
        cmd.add("cf0M")
        cmd.add(FileUtil.resolvePath(workPath, "..", jarFile!!.name))
        cmd.add("*")

        //        getLog().info("当前目录：" + workPath);
        var bash_cmd = cmd.joinToString(" ")
        log.info(bash_cmd)

//        var bash_cmd = ArrayList<String>()
//        val isWindows = System.getProperty("os.name").contains("windows", true);
//
//        if (!isWindows) {
//            bash_cmd.add("/bin/bash")
//            bash_cmd.add("-c")
//            bash_cmd.add(java.lang.String.join(" ", cmd))
//        } else {
//            bash_cmd = cmd
//        }

        val result = ShellUtil.execRuntimeCommand(bash_cmd, workPath)
        if (result.hasError) {
            throw RuntimeException(result.msg)
        }
    }

    private fun extractJar(jarFileName: String) {
        val splitLibPath = File(FileUtil.resolvePath(outputDirectory!!.path, "split-lib"))


        val workJar = File(FileUtil.resolvePath(splitLibPath.path, "jar"))
        workJar.mkdirs()
        File(FileUtil.resolvePath(splitLibPath.path, "lib")).mkdirs()


        //        new File(FileUtil.joinPath(splitLibPath.getPath(), "tmp")).mkdirs();
        val cmd = ArrayList<String>()
        cmd.add(jdkJarCmdPath)
        cmd.add("xf")
        cmd.add(jarFileName)

        var bash_cmd = cmd.joinToString(" ")
        log.info(bash_cmd)


        val result = ShellUtil.execRuntimeCommand(bash_cmd, workJar.path)
        if (result.hasError) {
            throw RuntimeException(result.msg)
        }
    }

    @SneakyThrows
    private fun getJarGroupId(file: File): String {
        val jarFile = JarFile(file)

        val entries = jarFile.entries()
        while (entries.hasMoreElements()) {
            val jarEntry = entries.nextElement()
            if (jarEntry.isDirectory) {
                continue
            }
            if (jarEntry.name.startsWith("META-INF/maven")
                && jarEntry.name.endsWith("/pom.properties")
            ) {
                val inputStream = jarFile.getInputStream(jarEntry)

                val bytes = ByteArray(inputStream.available())
                inputStream.read(bytes)

                inputStream.close()
                jarFile.close()
                return getGroupIdFromPomProperties(String(bytes, StandardCharsets.UTF_8))
            }
        }
        jarFile.close()
        return ""
    }

    private fun getGroupIdFromPomProperties(content: String): String {
        val lines = content.split("\n".toRegex()).dropLastWhile { it.isEmpty() }
        for (l in lines) {
            if (l.startsWith("groupId=")) {
                return l.split("=".toRegex()).dropLastWhile { it.isEmpty() }.last().trim()
            }
        }
        return ""
    }

    private fun getJarGroupIdWithUnzip(jar: File): String {
        val tmpPath = File(FileUtil.resolvePath(outputDirectory!!.path, "split-lib", "tmp", jar.name))
        if (tmpPath.mkdirs() == false) {
            throw RuntimeException("创建文件夹失败：" + tmpPath.path)
        }

        val cmd = ArrayList<String>()
        cmd.add(jdkJarCmdPath)
        cmd.add("xf")
        cmd.add(jar.path)

        var bash_cmd = cmd.joinToString(" ")
        log.info(bash_cmd)

        val result = ShellUtil.execRuntimeCommand(bash_cmd, tmpPath.path)
        if (result.hasError) {
            throw RuntimeException(result.msg)
        }

        val startFile = File(FileUtil.resolvePath(tmpPath.path, "META-INF", "maven"))
        if (startFile.exists() == false) {
            return ""
        }
        val pomPropertyFile = getPomPropertyFile(startFile) ?: return ""

        val content = pomPropertyFile.readText()
        return getGroupIdFromPomProperties(content)
    }

    private fun getPomPropertyFile(tmpPath: File): File? {
        val files = tmpPath.listFiles() ?: return null

        for (f in files) {
            if (f.isFile) {
                if (f.name == "pom.properties") {
                    return f
                }
            } else {
                val t = getPomPropertyFile(f)
                if (t != null) {
                    return t
                }
            }
        }

        return null
    }
}
